home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 7 / Amiga Format AFCD07 (Dec 1996, Issue 91).iso / serious / shareware / programming / emacs-complete / fsf / emacs / lisp / ispell4.el < prev    next >
Lisp/Scheme  |  1994-07-23  |  39KB  |  1,090 lines

  1. ;;; ispell.el --- this is the GNU EMACS interface to GNU ISPELL version 4.
  2.  
  3. ;;Copyright (C) 1990, 1991, 1993 Free Software Foundation, Inc.
  4.  
  5. ;; Keywords: wp
  6.  
  7. ;;This file is part of GNU Emacs.
  8. ;;
  9. ;;GNU Emacs is free software; you can redistribute it and/or modify
  10. ;;it under the terms of the GNU General Public License as published by
  11. ;;the Free Software Foundation; either version 2, or (at your option)
  12. ;;any later version.
  13. ;;
  14. ;;GNU Emacs is distributed in the hope that it will be useful,
  15. ;;but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. ;;GNU General Public License for more details.
  18. ;;
  19. ;;You should have received a copy of the GNU General Public License
  20. ;;along with GNU Emacs; see the file COPYING.  If not, write to
  21. ;;the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  22.  
  23. ;;; Commentary:
  24.  
  25. ;; This package provides a graceful interface to ispell, the GNU
  26. ;; spelling checker.
  27.  
  28. ;;; Code:
  29.  
  30. (defvar ispell-have-new-look t
  31.   "Non-nil means use the `-r' option when running `look'.")
  32.  
  33. (defvar ispell-enable-tex-parser nil
  34.   "Non-nil enables experimental TeX parser in Ispell for TeX-mode buffers.")
  35.  
  36. (defvar ispell-process nil "The process running Ispell")
  37. (defvar ispell-next-message nil
  38.   "An integer: where in `*ispell*' buffer to find next message from Ispell.")
  39.  
  40. (defvar ispell-command "ispell"
  41.   "Command for running Ispell.")
  42. (defvar ispell-command-options nil
  43.   "*String (or list of strings) to pass to Ispell as command arguments.
  44. You can specify your private dictionary via the -p <filename> option.
  45. The -S option is always passed to Ispell as the last parameter,
  46. and need not be mentioned here.")
  47.  
  48. (defvar ispell-look-command "look"
  49.   "*Command for running look.")
  50.  
  51. ;Each marker in this list points to the start of a word that
  52. ;ispell thought was bad last time it did the :file command.
  53. ;Notice that if the user accepts or inserts a word into his
  54. ;private dictionary, then some "good" words will be on the list.
  55. ;We would like to deal with this by looking up the words again just before
  56. ;presenting them to the user, but that is too slow on machines
  57. ;without the select system call.  Therefore, see the variable
  58. ;ispell-recently-accepted.
  59. (defvar ispell-bad-words nil
  60.   "A list of markers reflecting the output of the Ispell `:file' command.")
  61.  
  62. ;list of words that the user has accepted, but that might still
  63. ;be on the bad-words list
  64. (defvar ispell-recently-accepted nil)
  65.  
  66. ;; Non-nil means we have started showing an alternatives window.
  67. ;; This is the window config from before then.
  68. (defvar ispell-window-configuration nil)
  69.  
  70. ;t when :dump command needed
  71. (defvar ispell-dump-needed nil)
  72.  
  73. (defun ispell-flush-bad-words ()
  74.   (while ispell-bad-words
  75.     (if (markerp (car ispell-bad-words))
  76.         (set-marker (car ispell-bad-words) nil))
  77.     (setq ispell-bad-words (cdr ispell-bad-words)))
  78.   (setq ispell-recently-accepted nil))
  79.  
  80. (defun kill-ispell ()
  81.   "Kill the Ispell process.
  82. Any changes in your private dictionary
  83. that have not already been dumped will be lost."
  84.   (interactive)
  85.   (if ispell-process
  86.       (delete-process ispell-process))
  87.   (setq ispell-process nil)
  88.   (ispell-flush-bad-words))
  89.  
  90. (put 'ispell-startup-error 'error-conditions
  91.      '(ispell-startup-error error))
  92. (put 'ispell-startup-error 'error-message
  93.      "Problem starting ispell - see buffer *ispell*")
  94.  
  95. ;; Start an ispell subprocess; check the version; and display the greeting.
  96.  
  97. (defun start-ispell ()
  98.   (message "Starting ispell ...")
  99.   (let ((buf (get-buffer "*ispell*")))
  100.     (if buf
  101.     (kill-buffer buf)))
  102.   (condition-case err
  103.       (setq ispell-process
  104.         (apply 'start-process "ispell" "*ispell*" ispell-command
  105.            (append (if (listp ispell-command-options)
  106.                    ispell-command-options
  107.                  (list ispell-command-options))
  108.                '("-S"))))
  109.     (file-error (signal 'ispell-startup-error nil)))
  110.   (process-kill-without-query ispell-process)
  111.   (buffer-disable-undo (process-buffer ispell-process))
  112.   (accept-process-output ispell-process)
  113.   (let (last-char)
  114.     (save-excursion
  115.       (set-buffer (process-buffer ispell-process))
  116.       (bury-buffer (current-buffer))
  117.       (setq last-char (- (point-max) 1))
  118.       (while (not (eq (char-after last-char) ?=))
  119.     (cond ((not (eq (process-status ispell-process) 'run))
  120.            (kill-ispell)
  121.            (signal 'ispell-startup-error nil)))
  122.     (accept-process-output ispell-process)
  123.     (setq last-char (- (point-max) 1)))
  124.       (goto-char (point-min))
  125.       (let ((greeting (read (current-buffer))))
  126.     (if (not (= (car greeting) 1))
  127.         (error "Bad ispell version: wanted 1, got %d" (car greeting)))
  128.     (message (car (cdr greeting))))
  129.       (delete-region (point-min) last-char))))
  130.   
  131. ;; Make sure ispell is ready for a command.
  132. ;; Leaves buffer set to *ispell*, point at '='.
  133.  
  134. (defun ispell-sync (intr)
  135.   (if (or (null ispell-process)
  136.       (not (eq (process-status ispell-process) 'run)))
  137.       (start-ispell))
  138.   (if intr
  139.       (interrupt-process ispell-process))
  140.   (let (last-char)
  141.     (set-buffer (process-buffer ispell-process))
  142.     (bury-buffer (current-buffer))
  143.     (setq last-char (- (point-max) 1))
  144.     (while (not (eq (char-after last-char) ?=))
  145.       (accept-process-output ispell-process)
  146.       (setq last-char (- (point-max) 1)))
  147.     (goto-char last-char)))
  148.  
  149. ;; Send a command to ispell.  Choices are:
  150. ;; 
  151. ;; WORD        Check spelling of WORD.  Result is
  152. ;; 
  153. ;;             nil               not found
  154. ;;             t               spelled ok
  155. ;;             list of strings           near misses
  156. ;; 
  157. ;; :file FILENAME    scan the named file, and print the file offsets of
  158. ;;         any misspelled words
  159. ;; 
  160. ;; :insert WORD    put word in private dictionary
  161. ;; 
  162. ;; :accept WORD    don't complain about word any more this session
  163. ;; 
  164. ;; :dump        write out the current private dictionary, if necessary.
  165. ;; 
  166. ;; :reload        reread private dictionary (default: `~/ispell.words')
  167. ;; 
  168. ;; :tex
  169. ;; :troff
  170. ;; :generic    set type of parser to use when scanning whole files
  171.  
  172. (defun ispell-cmd (&rest strings)
  173.   (save-excursion
  174.     (ispell-sync t)
  175.     (set-buffer (process-buffer ispell-process))
  176.     (bury-buffer (current-buffer))
  177.     (erase-buffer)
  178.     (setq ispell-next-message (point-min))
  179.     (while strings
  180.       (process-send-string ispell-process (car strings))
  181.       (setq strings (cdr strings)))
  182.     (process-send-string ispell-process "\n")
  183.     (accept-process-output ispell-process)
  184.     (ispell-sync nil)))
  185.  
  186. (defun ispell-dump ()
  187.   (cond (ispell-dump-needed
  188.      (setq ispell-dump-needed nil)
  189.      (ispell-cmd ":dump"))))
  190.  
  191. (defun ispell-insert (word)
  192.   (ispell-cmd ":insert " word)
  193.   (if ispell-bad-words
  194.       (setq ispell-recently-accepted (cons word ispell-recently-accepted)))
  195.   (setq ispell-dump-needed t))
  196.  
  197. (defun ispell-accept (word)
  198.   (ispell-cmd ":accept " word)
  199.   (if ispell-bad-words
  200.       (setq ispell-recently-accepted (cons word ispell-recently-accepted))))
  201.  
  202. ;; Return the next message sent by the Ispell subprocess.
  203.  
  204. (defun ispell-next-message ()
  205.   (save-excursion
  206.     (set-buffer (process-buffer ispell-process))
  207.     (bury-buffer (current-buffer))
  208.     (save-restriction
  209.       (goto-char ispell-next-message)
  210.       (narrow-to-region (point)
  211.                         (progn (forward-sexp 1) (point)))
  212.       (setq ispell-next-message (point))
  213.       (goto-char (point-min))
  214.       (read (current-buffer)))))
  215.  
  216. (defun ispell-tex-buffer-p ()
  217.   (memq major-mode '(plain-tex-mode latex-mode slitex-mode)))
  218.  
  219. (defvar ispell-menu-map (make-sparse-keymap "Spell"))
  220. (defalias 'ispell-menu-map ispell-menu-map)
  221.  
  222. (define-key ispell-menu-map [ispell-complete-word-interior-frag]
  223.   '("Complete Interior Fragment" . ispell-complete-word-interior-frag))
  224.  
  225. (define-key ispell-menu-map [ispell-complete-word]
  226.   '("Complete Word" . ispell-complete-word))
  227.  
  228. (define-key ispell-menu-map [reload-ispell]
  229.   '("Reload Dictionary" . reload-ispell))
  230.  
  231. (define-key ispell-menu-map [ispell-next]
  232.   '("Continue Check" . ispell-next))
  233.  
  234. (define-key ispell-menu-map [ispell-message]
  235.   '("Check Message" . ispell-message))
  236.  
  237. (define-key ispell-menu-map [ispell-region]
  238.   '("Check Region" . ispell-region))
  239.  
  240. (define-key ispell-menu-map [ispell-buffer]
  241.   '("Check Buffer" . ispell))
  242.  
  243. (define-key ispell-menu-map [ispell-word]
  244.   '("Check Word" . ispell-word))
  245.  
  246. ;;;###autoload
  247. (defun ispell (&optional buf start end)
  248.   "Run Ispell over current buffer's visited file.
  249. First the file is scanned for misspelled words, then Ispell
  250. enters a loop with the following commands for every misspelled word:
  251.  
  252. DIGIT    Near miss selector.  If the misspelled word is close to
  253.     some words in the dictionary, they are offered as near misses.
  254. r    Replace.  Replace the word with a string you type.  Each word
  255.     of your new string is also checked.
  256. i    Insert.  Insert this word in your private dictionary (by default,
  257.     `$HOME/ispell.words').
  258. a    Accept.  Accept this word for the rest of this editing session,
  259.      but don't put it in your private dictionary.
  260. l    Lookup.  Look for a word in the dictionary by fast binary
  261.     search, or search for a regular expression in the dictionary
  262.     using grep.
  263. SPACE    Accept the word this time, but complain if it is seen again.
  264. q, \\[keyboard-quit]    Leave the command loop.  You can come back later with \\[ispell-next]."
  265.   (interactive)
  266.   (if (null start)
  267.       (setq start 0))
  268.   (if (null end)
  269.       (setq end 0))
  270.  
  271.   (if (null buf)
  272.       (setq buf (current-buffer)))
  273.   (setq buf (get-buffer buf))
  274.   (if (null buf)
  275.       (error "Can't find buffer"))
  276.   ;; Deactivate the mark, because we'll do it anyway if we change something,
  277.   ;; and a region highlight while in the Ispell loop is distracting.
  278.   (deactivate-mark)
  279.   (save-excursion
  280.     (set-buffer buf)
  281.     (let ((filename buffer-file-name)
  282.       (delete-temp nil))
  283.       (unwind-protect
  284.       (progn
  285.         (cond ((or (null filename)
  286.                (find-file-name-handler buffer-file-name nil))
  287.            (setq filename (make-temp-name "/usr/tmp/ispell"))
  288.            (setq delete-temp t)
  289.            (write-region (point-min) (point-max) filename))
  290.           ((and (buffer-modified-p buf)
  291.             (y-or-n-p (format "Save file %s? " filename)))
  292.            (save-buffer)))
  293.         (message "Ispell scanning file...")
  294.         (if (and ispell-enable-tex-parser
  295.              (ispell-tex-buffer-p))
  296.         (ispell-cmd ":tex")
  297.           (ispell-cmd ":generic"))
  298.         (ispell-cmd (format ":file %s %d %d" filename start end)))
  299.     (if delete-temp
  300.         (condition-case ()
  301.         (delete-file filename)
  302.           (file-error nil)))))
  303.     (message "Parsing ispell output ...")
  304.     (ispell-flush-bad-words)
  305.     (let (pos bad-words)
  306.       (while (numberp (setq pos (ispell-next-message)))
  307.     ;;ispell may check the words on the line following the end
  308.     ;;of the region - therefore, don't record anything out of range
  309.     (if (or (= end 0)
  310.         (< pos end))
  311.         (setq bad-words (cons (set-marker (make-marker) (+ pos 1))
  312.                   bad-words))))
  313.       (setq bad-words (cons pos bad-words))
  314.       (setq ispell-bad-words (nreverse bad-words))))
  315.   (cond ((not (markerp (car ispell-bad-words)))
  316.      (setq ispell-bad-words nil)
  317.      (message "No misspellings.")
  318.      t)
  319.     (t
  320.      (message "Ispell parsing done.")
  321.      (ispell-next))))
  322.  
  323. ;;;###autoload
  324. (defalias 'ispell-buffer 'ispell)
  325.  
  326. (defun ispell-next ()
  327.   "Resume command loop for most recent Ispell command.
  328. Return value is t unless exit is due to typing `q'."
  329.   (interactive)
  330.   (setq ispell-window-configuration nil)
  331.   (prog1
  332.       (unwind-protect
  333.       (catch 'ispell-quit
  334.         ;; There used to be a save-excursion here,
  335.         ;; but that was annoying: it's better if point doesn't move
  336.         ;; when you type q.
  337.         (let (next)
  338.           (while (markerp (setq next (car ispell-bad-words)))
  339.         (switch-to-buffer (marker-buffer next))
  340.         (push-mark)
  341.         (ispell-point next "at saved position.")
  342.         (setq ispell-bad-words (cdr ispell-bad-words))
  343.         (set-marker next nil)))
  344.         t)
  345.     (ispell-dehighlight)
  346.     (if ispell-window-configuration
  347.         (set-window-configuration ispell-window-configuration))
  348.     (cond ((null ispell-bad-words)
  349.            (error "Ispell has not yet been run"))
  350.           ((markerp (car ispell-bad-words))
  351.            (message (substitute-command-keys
  352.                "Type \\[ispell-next] to continue")))
  353.           ((eq (car ispell-bad-words) nil)
  354.            (setq ispell-bad-words nil)
  355.            (message "No more misspellings (but checker was interrupted)"))
  356.           ((eq (car ispell-bad-words) t)
  357.            (setq ispell-bad-words nil)
  358.            (message "Ispell done"))
  359.           (t
  360.            (setq ispell-bad-words nil)
  361.            (message "Bad ispell internal list"))))
  362.     (ispell-dump)))
  363.  
  364. ;;;###autoload
  365. (defun ispell-word (&optional resume)
  366.   "Check the spelling of the word under the cursor.
  367. See the command `ispell' for more information.
  368. With a prefix argument, resume handling of the previous Ispell command."
  369.   (interactive "P")
  370.   (if resume
  371.       (ispell-next)
  372.     (condition-case err
  373.     (unwind-protect
  374.         (catch 'ispell-quit
  375.           (save-window-excursion
  376.         (ispell-point (point) "at point."))
  377.           (ispell-dump))
  378.       (ispell-dehighlight))
  379.       (ispell-startup-error
  380.        (cond ((y-or-n-p "Problem starting ispell, use old-style spell instead? ")
  381.           (load-library "spell")
  382.           (define-key esc-map "$" 'spell-word)
  383.           (spell-word)))))))
  384.  
  385. ;;;###autoload (define-key esc-map "$" 'ispell-word)
  386.  
  387. ;;;###autoload
  388. (defun ispell-region (start &optional end)
  389.   "Check the spelling for all of the words in the region."
  390.   (interactive "r")
  391.   (ispell (current-buffer) start end))
  392.  
  393. (defun ispell-letterp (c)
  394.   (and c
  395.        (or (and (>= c ?A) (<= c ?Z))
  396.        (and (>= c ?a) (<= c ?z))
  397.        (>= c 128))))
  398.  
  399. (defun ispell-letter-or-quotep (c)
  400.   (and c
  401.        (or (and (>= c ?A) (<= c ?Z))
  402.        (and (>= c ?a) (<= c ?z))
  403.        (= c ?')
  404.        (>= c 128))))
  405.  
  406. (defun ispell-find-word-start ()
  407.   ;;backward to a letter
  408.   (if (not (ispell-letterp (char-after (point))))
  409.       (while (and (not (bobp))
  410.           (not (ispell-letterp (char-after (- (point) 1)))))
  411.     (backward-char)))
  412.   ;;backward to beginning of word
  413.   (while (ispell-letter-or-quotep (char-after (- (point) 1)))
  414.     (backward-char))
  415.   (skip-chars-forward "'"))
  416.  
  417. (defun ispell-find-word-end ()
  418.   (while (ispell-letter-or-quotep (char-after (point)))
  419.     (forward-char))
  420.   (skip-chars-backward "'"))
  421.  
  422. (defun ispell-next-word ()
  423.   (while (and (not (eobp))
  424.           (not (ispell-letterp (char-after (point)))))
  425.     (forward-char)))
  426.  
  427. ;if end is nil, then do one word at start
  428. ;otherwise, do all words from the beginning of the word where
  429. ;start points, to the end of the word where end points
  430. (defun ispell-point (start message)
  431.   (let ((wend (make-marker))
  432.     rescan
  433.     end)
  434.     ;; There used to be a save-excursion here,
  435.     ;; but that was annoying: it's better if point doesn't move
  436.     ;; when you type q.
  437.     (goto-char start)
  438.     (ispell-find-word-start)        ;find correct word start
  439.     (setq start (point-marker))
  440.     (ispell-find-word-end)        ;now find correct end
  441.     (setq end (point-marker))
  442.     ;; Do nothing if we don't find a word.
  443.     (if (< start end)
  444.     (while (< start end)
  445.       (goto-char start)
  446.       (ispell-find-word-end)    ;find end of current word
  447.                     ;could be before 'end' if
  448.                     ;user typed replacement
  449.                     ;that is more than one word
  450.       (set-marker wend (point))
  451.       (setq rescan nil)
  452.       (setq word (buffer-substring start wend))
  453.       (cond ((ispell-still-bad word)
  454. ;;; This just causes confusion. -- rms.
  455. ;;;         (goto-char start)
  456. ;;;         (sit-for 0)
  457.          (message (format "Ispell checking %s" word))
  458.          (ispell-cmd word)
  459.          (let ((message (ispell-next-message)))
  460.            (cond ((eq message t)
  461.               (message "%s: ok" word))
  462.              ((or (null message)
  463.                   (consp message))
  464.               (setq rescan
  465.                 (ispell-command-loop word start wend message)))
  466.              (t
  467.               (error "unknown ispell response %s" message))))))
  468.       (cond ((null rescan)
  469.          (goto-char wend)
  470.          (ispell-next-word)
  471.          (set-marker start (point))))))
  472.     ;;clear the choices buffer; otherwise it's hard for the user to tell
  473.     ;;when we get back to the command loop
  474.     (let ((buf (get-buffer "*ispell choices*")))
  475.       (cond (buf
  476.          (set-buffer buf)
  477.          (erase-buffer))))
  478.     (set-marker start nil)
  479.     (set-marker end nil)
  480.     (set-marker wend nil)))
  481.   
  482. (defun ispell-still-bad (word)
  483.   (let ((words ispell-recently-accepted)
  484.     (ret t)
  485.     (case-fold-search t))
  486.     (while words
  487.       (cond ((eq (string-match (car words) word) 0)
  488.          (setq ret nil)
  489.          (setq words nil)))
  490.       (setq words (cdr words)))
  491.     ret))
  492.  
  493. (defun ispell-show-choices (word message first-line)
  494.   ;;if there is only one window on the frame, make the ispell
  495.   ;;messages winow be small.  otherwise just use the other window
  496.   (let* ((selwin (selected-window))
  497.      (resize (eq selwin (next-window)))
  498.      (buf (get-buffer-create "*ispell choices*"))
  499.      w)
  500.     (or ispell-window-configuration
  501.     (setq ispell-window-configuration (current-window-configuration)))
  502.     (setq w (display-buffer buf))
  503.     (buffer-disable-undo buf)
  504.     (if resize
  505.     (unwind-protect
  506.         (progn
  507.           (select-window w)
  508.           (enlarge-window (- 6 (window-height w))))
  509.       (select-window selwin)))
  510.     (save-excursion
  511.       (set-buffer buf)
  512.       (bury-buffer buf)
  513.       (set-window-point w (point-min))
  514.       (set-window-start w (point-min))
  515.       (erase-buffer)
  516.       (insert first-line "\n")
  517.       (insert
  518.        "SPC skip; A accept; I insert; DIGIT select; R replace; \
  519. L lookup; Q quit\n")
  520.       (cond ((not (null message))
  521.          (let ((i 0))
  522.            (while (< i 3)
  523.          (let ((j 0))
  524.            (while (< j 3)
  525.              (let* ((n (+ (* j 3) i))
  526.                 (choice (nth n message)))
  527.                (cond (choice
  528.                   (let ((str (format "%d %s" n choice)))
  529.                 (insert str)
  530.                 (insert-char ?  (- 20 (length str)))))))
  531.              (setq j (+ j 1))))
  532.          (insert "\n")
  533.          (setq i (+ i 1)))))))))
  534.  
  535. (defun ispell-command-loop (word start end message)
  536.   (let ((flag t)
  537.     (rescan nil)
  538.     first-line)
  539.     (if (null message)
  540.     (setq first-line (concat "No near misses for '" word "'"))
  541.       (setq first-line (concat "Near misses for '" word "'")))
  542.     (ispell-highlight start end)
  543.     (while flag
  544.       (ispell-show-choices word message first-line)
  545.       (message "Ispell command: ")
  546.       (undo-boundary)
  547.       (let ((c (downcase (read-char)))
  548.         replacement)
  549.     (cond ((and (>= c ?0)
  550.             (<= c ?9)
  551.             (setq replacement (nth (- c ?0) message)))
  552.            (ispell-replace start end replacement)
  553.            (setq flag nil))
  554.           ((= c ?q)
  555.            (throw 'ispell-quit nil))
  556.           ((= c (nth 3 (current-input-mode)))
  557.            (keyboard-quit))
  558.           ((= c ? )
  559.            (setq flag nil))
  560.           ((= c ?r)
  561.            (ispell-replace start end (read-string "Replacement: "))
  562.            (setq rescan t)
  563.            (setq flag nil))
  564.           ((= c ?i)
  565.            (ispell-insert word)
  566.            (setq flag nil))
  567.           ((= c ?a)
  568.            (ispell-accept word)
  569.            (setq flag nil))
  570.           ((= c ?l)
  571.            (let ((val (ispell-do-look word)))
  572.          (setq first-line (car val))
  573.          (setq message (cdr val))))
  574.           ((= c ??)
  575.            (message
  576.         "Type 'C-h d ispell' to the emacs main loop for more help")
  577.            (sit-for 2))
  578.           (t
  579.            (message "Bad ispell command")
  580.            (sit-for 2)))))
  581.     rescan))
  582.  
  583. (defun ispell-do-look (bad-word)
  584.   (let (regex buf words)
  585.     (cond ((null ispell-have-new-look)
  586.        (setq regex (read-string "Lookup: ")))
  587.       (t
  588.        (setq regex (read-string "Lookup (regex): " "^"))))
  589.     (setq buf (get-buffer-create "*ispell look*"))
  590.     (save-excursion
  591.       (set-buffer buf)
  592.       (delete-region (point-min) (point-max))
  593.       (if ispell-have-new-look
  594.       (call-process ispell-look-command nil buf nil "-r" regex)
  595.     (call-process ispell-look-command nil buf nil regex))
  596.       (goto-char (point-min))
  597.       (forward-line 10)
  598.       (delete-region (point) (point-max))
  599.       (goto-char (point-min))
  600.       (while (not (= (point-min) (point-max)))
  601.     (end-of-line)
  602.     (setq words (cons (buffer-substring (point-min) (point)) words))
  603.     (forward-line)
  604.     (delete-region (point-min) (point)))
  605.       (kill-buffer buf)
  606.       (cons (format "Lookup '%s'" regex)
  607.         (reverse words)))))
  608.     
  609. (defun ispell-replace (start end new)
  610.   (goto-char start)
  611.   (insert new)
  612.   (delete-region (point) end))
  613.  
  614. (defun reload-ispell ()
  615.   "Tell Ispell to re-read your private dictionary."
  616.   (interactive)
  617.   (ispell-cmd ":reload"))
  618.  
  619. (defun batch-make-ispell ()
  620.   (byte-compile-file "ispell.el")
  621.   (find-file "ispell.texinfo")
  622.   (let ((old-dir default-directory)
  623.     (default-directory "/tmp"))
  624.     (texinfo-format-buffer))
  625.   (Info-validate)
  626.   (if (get-buffer " *problems in info file*")
  627.       (kill-emacs 1))
  628.   (write-region (point-min) (point-max) "ispell.info"))
  629.  
  630. (defvar ispell-highlight t
  631.   "*Non-nil means to highlight ispell words.")
  632.  
  633. (defvar ispell-overlay nil)
  634.  
  635. (defun ispell-dehighlight ()
  636.   (and ispell-overlay
  637.        (progn
  638.      (delete-overlay ispell-overlay)
  639.      (setq ispell-overlay nil))))
  640.  
  641. (defun ispell-highlight (start end)
  642.   (and ispell-highlight 
  643.        window-system
  644.        (progn
  645.      (or ispell-overlay
  646.          (progn
  647.            (setq ispell-overlay (make-overlay start end))
  648.            (overlay-put ispell-overlay 'face
  649.                 (if (internal-find-face 'ispell)
  650.                 'ispell 'region))))
  651.      (move-overlay ispell-overlay start end (current-buffer)))))
  652.  
  653. ;;;; ispell-complete-word
  654.  
  655. ;;; Brief Description:
  656. ;;; Complete word fragment at point using dictionary and replace with full
  657. ;;; word.  Expansion done in current buffer like lisp-complete-symbol.
  658. ;;; Completion of interior word fragments possible with prefix argument.
  659.  
  660. ;;; Known Problem: 
  661. ;;; Does not use private dictionary because GNU `look' does not use it.  It
  662. ;;; would be nice if GNU `look' took standard input; this would allow gzip'ed
  663. ;;; dictionaries to be used.  GNU `look' also has a bug, see
  664. ;;; `ispell-gnu-look-still-broken-p'.
  665.  
  666. ;;; Motivation: 
  667. ;;; The `l', "regular expression look up", keymap option of ispell-word
  668. ;;; (ispell-do-look) can only be run after finding a misspelled word.  So
  669. ;;; ispell-do-look can not be used to look for words starting with `cat' to
  670. ;;; find `catechetical' since `cat' is a correctly spelled word.  Furthermore,
  671. ;;; ispell-do-look does not return the entire list returned by `look'.
  672. ;;;  
  673. ;;; ispell-complete-word allows you to get a completion list from the system
  674. ;;; dictionary and expand a word fragment at the current position in a buffer.
  675. ;;; These examples assume ispell-complete-word is bound to M-TAB as it is in
  676. ;;; text-mode; the `Complete Word' and `Complete Interior Fragment' entries of
  677. ;;; the "Spell" submenu under the "Edit" menu may also be used instead of
  678. ;;; M-TAB and C-u M-TAB, respectively.
  679. ;;;
  680. ;;;   EXAMPLE 1: The word `Saskatchewan' needs to be spelled.  The user may
  681. ;;;   type `Sas' and hit M-TAB and a completion list will be built using the
  682. ;;;   shell command `look' and displayed in the *Completions* buffer:
  683. ;;;
  684. ;;;        Possible completions are:
  685. ;;;        sash                               sashay
  686. ;;;        sashayed                           sashed
  687. ;;;        sashes                             sashimi
  688. ;;;        Saskatchewan                       Saskatoon
  689. ;;;        sass                               sassafras
  690. ;;;        sassier                            sassing
  691. ;;;        sasswood                           sassy
  692. ;;;
  693. ;;;   By viewing this list the user will hopefully be motivated to insert the
  694. ;;;   letter `k' after the `sas'.  When M-TAB is hit again the word `Saskat'
  695. ;;;   will be inserted in place of `sas' (note case) since this is a unique
  696. ;;;   substring completion.  The narrowed completion list can be viewed with
  697. ;;;   another M-TAB
  698. ;;;
  699. ;;;        Possible completions are:
  700. ;;;        Saskatchewan                       Saskatoon
  701. ;;;
  702. ;;;   Inserting the letter `c' and hitting M-TAB will narrow the completion
  703. ;;;   possibilities to just `Saskatchewan' and this will be inserted in the
  704. ;;;   buffer.  At any point the user may click the mouse on a completion to
  705. ;;;   select it.
  706. ;;;
  707. ;;;   EXAMPLE 2: The user has typed `Sasaquane' and M-$ (ispell-word) gives no
  708. ;;;   "near-misses" in which case you back up to `Sas' and hit M-TAB and find
  709. ;;;   the correct word as above.  The `Sas' will be replaced by `Saskatchewan'
  710. ;;;   and the remaining word fragment `aquane' can be deleted.
  711. ;;;
  712. ;;;   EXAMPLE 3: If a version of `look' is used that supports regular
  713. ;;;   expressions, then `ispell-have-new-look' should be t (its default) and
  714. ;;;   interior word fragments may also be used for the search.  The word
  715. ;;;   `pneumonia' needs to be spelled.  The user can only remember the
  716. ;;;   interior fragment `mon' in which case `C-u M-TAB' on `mon' gives a list
  717. ;;;   of all words containing the interior word fragment `mon'.  Typing `p'
  718. ;;;   and M-TAB will narrow this list to all the words starting with `p' and
  719. ;;;   containing `mon' from which `pneumonia' can be found as above.
  720.  
  721. ;;; The user-defined variables are:
  722. ;;;
  723. ;;;  ispell-look-command
  724. ;;;  ispell-look-dictionary
  725. ;;;  ispell-gnu-look-still-broken-p
  726.  
  727. ;;; Algorithm (some similarity to lisp-complete-symbol):
  728. ;;;  
  729. ;;; * call-process on command ispell-look-command (default: "look") to find
  730. ;;;   words in ispell-look-dictionary matching `string' (or `regexp' if 
  731. ;;;   ispell-have-new-look is t).  Parse output and store results in 
  732. ;;;   ispell-lookup-completions-alist.
  733. ;;; 
  734. ;;; * Build completion list using try-completion and `string'
  735. ;;; 
  736. ;;; * Replace `string' in buffer with matched common substring completion.
  737. ;;; 
  738. ;;; * Display completion list only if there is no matched common substring.
  739. ;;; 
  740. ;;; * Rebuild ispell-lookup-completions-alist, on a next call, only when
  741. ;;;   beginning of word fragment has changed.
  742. ;;;  
  743. ;;; * Interior fragments searches are performed similarly with the exception
  744. ;;;   that the entire fragment at point is initially removed from the buffer,
  745. ;;;   the STRING passed to try-completion and all-completions is just "" and
  746. ;;;   not the interior fragment; this allows all completions containing the
  747. ;;;   interior fragment to be shown.  The location in the buffer is stored to
  748. ;;;   decide whether future completion narrowing of the current list should be
  749. ;;;   done or if a new list should be built.  See interior fragment example
  750. ;;;   above.
  751. ;;;
  752. ;;; * Robust searches are done using a `look' with -r (regular expression) 
  753. ;;;   switch if ispell-have-new-look is t.
  754.  
  755. ;;;; User-defined variables.
  756.  
  757. (defvar ispell-look-dictionary nil
  758.   "*If non-nil then spelling dictionary as string for `ispell-complete-word'.
  759. Overrides default dictionary file such as \"/usr/dict/words\" or GNU look's
  760. \"${prefix}/lib/ispell/ispell.words\"")
  761.  
  762. (defvar ispell-gnu-look-still-broken-p nil
  763.   "*t if GNU look -r can give different results with and without trialing `.*'.
  764. Example: `look -dfr \"^ya\" foo' returns nothing, while `look -dfr \"^ya.*\" foo'
  765. returns `yacc', where `foo' is a dictionary file containing the three lines
  766.  
  767.    y
  768.    y's
  769.    yacc
  770.  
  771. Both commands should return `yacc'.  If `ispell-complete-word' erroneously
  772. states that no completions exist for a string, then setting this variable to t
  773. will help find those completions.")
  774.  
  775. ;;;; Internal variables.
  776.  
  777. ;;; Possible completions for last word fragment.
  778. (defvar ispell-lookup-completions-alist nil)
  779.  
  780. ;;; Last word fragment processed by `ispell-complete-word'.
  781. (defvar ispell-lookup-last-word nil)
  782.  
  783. ;;; Buffer local variables.
  784.  
  785. ;;; Value of interior-frag in last call to `ispell-complete-word'.
  786. (defvar ispell-lookup-last-interior-p nil)
  787. (make-variable-buffer-local 'ispell-lookup-last-interior-p)
  788. (put 'ispell-lookup-last-interior-p 'permanent-local t)
  789.  
  790. ;;; Buffer position in last call to `ispell-complete-word'.
  791. (defvar ispell-lookup-last-bow nil)
  792. (make-variable-buffer-local 'ispell-lookup-last-bow)
  793. (put 'ispell-lookup-last-bow 'permanent-local t)
  794.  
  795. ;;;; Interactive functions.
  796. ;;;###autoload
  797. (defun ispell-complete-word (&optional interior-frag)
  798.   "Complete word using letters at point to word beginning using `look'.
  799. With optional argument INTERIOR-FRAG, word fragment at point is assumed to be
  800. an interior word fragment in which case `ispell-have-new-look' should be t.
  801. See also `ispell-look-dictionary' and `ispell-gnu-look-still-broken-p'."
  802.  
  803.   (interactive "P")
  804.  
  805.   ;; `look' must support regexp expressions in order to perform an interior
  806.   ;; fragment search.
  807.   (if (and interior-frag (not ispell-have-new-look))
  808.       (error (concat "Sorry, `ispell-have-new-look' is nil.  "
  809.                      "You also will need GNU Ispell's `look'.")))
  810.  
  811.   (let* ((completion-ignore-case t)
  812.  
  813.          ;; Get location of beginning of word fragment.
  814.          (bow (save-excursion (skip-chars-backward "a-zA-Z'") (point)))
  815.  
  816.          ;; Get the string to look up.
  817.          (string (buffer-substring bow (point)))
  818.  
  819.          ;; Get regexp for which we search and, if necessary, an interior word
  820.          ;; fragment.
  821.          (regexp (if interior-frag
  822.                      (concat "^.*" string ".*")
  823.                    ;; If possible use fast binary search: no trailing `.*'.
  824.                    (concat "^" string
  825.                            (if ispell-gnu-look-still-broken-p ".*"))))
  826.  
  827.          ;; We want all completions for case of interior fragments so set
  828.          ;; prefix to an empty string.
  829.          (prefix (if interior-frag "" string))
  830.  
  831.          ;; Are we continuing from a previous interior fragment search?
  832.          ;; Check last value of interior-word and if the point has moved.
  833.          (continuing-an-interior-frag-p
  834.           (and ispell-lookup-last-interior-p
  835.                (equal ispell-lookup-last-bow bow)))
  836.  
  837.          ;; Are we starting a unique word fragment search?  Always t for
  838.          ;; interior word fragment search.
  839.          (new-unique-string-p
  840.           (or interior-frag (null ispell-lookup-last-word)
  841.               (let ((case-fold-search t))
  842.                 ;; Can we locate last word fragment as a substring of current
  843.                 ;; word fragment?  If the last word fragment is larger than
  844.                 ;; the current string then we will have to rebuild the list
  845.                 ;; later.
  846.                 (not (string-match
  847.                       (concat "^" ispell-lookup-last-word) string)))))
  848.  
  849.          completion)
  850.  
  851.     ;; Check for perfect completion already.  That is, maybe the user has hit
  852.     ;; M-x ispell-complete-word one too many times?
  853.     (if (string-equal string "")
  854.         (if (string-equal (concat ispell-lookup-last-word " ")
  855.                           (buffer-substring
  856.                            (save-excursion (forward-word -1) (point)) (point)))
  857.             (error "Perfect match already")
  858.           (error "No word fragment at point")))
  859.  
  860.     ;; Create list of words from system dictionary starting with `string' if
  861.     ;; new string and not continuing from a previous interior fragment search.
  862.     (if (and (not continuing-an-interior-frag-p) new-unique-string-p)
  863.         (setq ispell-lookup-completions-alist
  864.               (ispell-lookup-build-list string regexp)))
  865.  
  866.     ;; Check for a completion of `string' in the list and store `string' and
  867.     ;; other variables for the next call.
  868.     (setq completion (try-completion prefix ispell-lookup-completions-alist)
  869.           ispell-lookup-last-word string
  870.           ispell-lookup-last-interior-p interior-frag
  871.           ispell-lookup-last-bow bow)
  872.  
  873.     ;; Test the completion status.
  874.     (cond
  875.  
  876.      ;; * Guess is a perfect match.
  877.      ((eq completion t)
  878.       (insert " ")
  879.       (message "Perfect match."))
  880.  
  881.      ;; * No possibilities.
  882.      ((null completion)
  883.       (message "Can't find completion for \"%s\"" string)
  884.       (beep))
  885.  
  886.      ;; * Replace string fragment with matched common substring completion.
  887.      ((and (not (string-equal completion ""))
  888.            ;; Fold case so a completion list is built when `string' and common
  889.            ;; substring differ only in case.
  890.            (let ((case-fold-search t))
  891.              (not (string-match (concat "^" completion "$") string))))
  892.       (search-backward string bow)
  893.       (replace-match completion nil t) ; FIXEDCASE doesn't work? or LITERAL?
  894.       (message "Proposed unique substring.  Repeat for completions list."))
  895.  
  896.      ;; * String is a common substring completion already.  Make list.
  897.      (t
  898.       (message "Making completion list...")
  899.       (if (string-equal completion "") (delete-region bow (point)))
  900.       (let ((list (all-completions prefix ispell-lookup-completions-alist)))
  901.         (with-output-to-temp-buffer "*Completions*"
  902.           (display-completion-list list)))
  903.       (message "Making completion list...done")))))
  904.  
  905. ;;;###autoload
  906. (defun ispell-complete-word-interior-frag ()
  907.   "Runs `ispell-complete-word' with a non-nil INTERIOR-FRAG.
  908. A completion list is built for word fragment at point which is assumed to be
  909. an interior word fragment.  `ispell-have-new-look' should be t."
  910.   (interactive)
  911.   (ispell-complete-word t))
  912.  
  913. ;;;; Internal Function.
  914.  
  915. ;;; Build list of words using ispell-look-command from dictionary
  916. ;;; ispell-look-dictionary (if this is a non-nil string).  Look for words
  917. ;;; starting with STRING if ispell-have-new-look is nil or look for REGEXP if
  918. ;;; ispell-have-new-look is t.  Returns result as an alist suitable for use by
  919. ;;; try-completion, all-completions, and completing-read.
  920. (defun ispell-lookup-build-list (string regexp)
  921.   (save-excursion
  922.     (message "Building list...")
  923.     (set-buffer (get-buffer-create " *ispell look*"))
  924.     (erase-buffer)
  925.  
  926.     (if (stringp ispell-look-dictionary)
  927.         (if ispell-have-new-look
  928.             (call-process ispell-look-command nil t nil "-fr" regexp
  929.                           ispell-look-dictionary)
  930.           (call-process ispell-look-command nil t nil "-f" string
  931.                         ispell-look-dictionary))
  932.       (if ispell-have-new-look
  933.           (call-process ispell-look-command nil t nil "-fr" regexp)
  934.         (call-process ispell-look-command nil t nil "-f" string)))
  935.  
  936.     ;; Build list for try-completion and all-completions by storing each line
  937.     ;; of output starting from bottom of buffer and deleting upwards.
  938.     (let (list)
  939.       (goto-char (point-min))
  940.       (while (not (= (point-min) (point-max)))
  941.         (end-of-line)
  942.         (setq list (cons (buffer-substring (point-min) (point)) list))
  943.         (forward-line)
  944.         (delete-region (point-min) (point)))
  945.  
  946.       ;; Clean.
  947.       (erase-buffer)
  948.       (message "Building list...done")
  949.  
  950.       ;; Make the list into an alist and return.
  951.       (mapcar 'list (nreverse list)))))
  952.  
  953. ;; Return regexp-quote of STRING if STRING is non-empty.
  954. ;; Otherwise return an unmatchable regexp.
  955. (defun ispell-non-empty-string (string)
  956.   (if (or (not string) (string-equal string ""))
  957.       "\\'\\`" ; An unmatchable string if string is null.
  958.     (regexp-quote string)))
  959.  
  960. (defvar ispell-message-cite-regexp "^   \\|^\t"
  961.   "*Regular expression to match lines cited from one message into another.")
  962.  
  963. (defvar ispell-message-text-end
  964.   (concat "^\\(" (mapconcat (function identity)
  965.                 '(
  966.                   ;; Matches postscript files.
  967.                   "%!PS-Adobe-2.0"
  968.                   ;; Matches uuencoded text
  969.                   "begin [0-9][0-9][0-9] .*\nM.*\nM.*\nM"
  970.                   ;; Matches shell files (esp. auto-decoding)
  971.                   "#! /bin/sh"
  972.                   ;; Matches difference listing
  973.                   "diff -c .*\n\\*\\*\\* .*\n--- "
  974.                   ;; Matches "--------------------- cut here"
  975.                   "[-=]+\\s cut here")
  976.                 "\\|")
  977.           "\\)")
  978.   "*End of text which will be checked in ispell-message.
  979. If it is a string, limit at first occurence of that regular expression.
  980. Otherwise, it must be a function which is called to get the limit.")
  981.  
  982. (defvar ispell-message-limit (* 100 80)
  983.   "*Ispell-message will check no more than this number of characters.")
  984.  
  985. ;;;###autoload
  986. (defun ispell-message ()
  987.   "Check the spelling of a mail message or news post.
  988. Don't check spelling of message headers (except subject) or included messages.
  989.  
  990. To spell-check whenever a message is sent, include this line in .emacs:
  991.    (setq news-inews-hook (setq mail-send-hook 'ispell-message))
  992.  
  993. Or you can bind the function to C-c i in gnus or mail with:
  994.    (setq mail-mode-hook (setq news-reply-mode-hook
  995.     (function (lambda () (local-set-key \"\\C-ci\" 'ispell-message)))))"
  996.   (interactive)
  997.   (save-excursion
  998.     (let (non-internal-message
  999.       (old-case-fold-search case-fold-search)
  1000.       (case-fold-search nil))
  1001.       (goto-char (point-min))
  1002.       ;; Don't spell-check the headers.
  1003.       (if (search-forward mail-header-separator nil t)
  1004.       ;; Move to first body line.
  1005.       (forward-line 1)
  1006.     (while (and (looking-at "[a-zA-Z-]+:\\|\t\\| ")
  1007.             (not (eobp)))
  1008.       (forward-line 1))
  1009.     (setq non-internal-message t)
  1010.     )
  1011.       (let* ((cite-regexp        ;Prefix of inserted text
  1012.          (cond
  1013.           ((featurep 'supercite)    ; sc 3.0
  1014.            (concat "\\(" (sc-cite-regexp) "\\)" "\\|"
  1015.                (ispell-non-empty-string sc-reference-tag-string)))
  1016.           ((featurep 'sc)        ; sc 2.3
  1017.            (concat "\\(" sc-cite-regexp "\\)" "\\|"
  1018.                (ispell-non-empty-string sc-reference-tag-string)))
  1019.           (non-internal-message    ; Assume nn sent us this message.
  1020.            (concat "In [a-zA-Z.]+ you write:" "\\|"
  1021.                "In <[^,;&+=]+> [^,;&+=]+ writes:" "\\|"
  1022.                " *> *"))
  1023.           ((equal major-mode 'news-reply-mode) ;Gnus
  1024.            (concat "In article <" "\\|"
  1025.                (if mail-yank-prefix
  1026.                (ispell-non-empty-string mail-yank-prefix)
  1027.              ispell-message-cite-regexp)))
  1028.           ((boundp 'vm-included-text-prefix) ; VM mail message
  1029.            (concat "[^,;&+=]+ writes:" "\\|"
  1030.                (ispell-non-empty-string vm-included-text-prefix)
  1031.                ))
  1032.           ((boundp 'mh-ins-buf-prefix) ; mh mail message
  1033.            (ispell-non-empty-string mh-ins-buf-prefix))
  1034.           (mail-yank-prefix            ; vanilla mail message.
  1035.            (ispell-non-empty-string mail-yank-prefix))
  1036.           (t ispell-message-cite-regexp)))
  1037.         (continue t)
  1038.         (limit
  1039.          (min
  1040.           (+ (point-min) ispell-message-limit)
  1041.           (point-max)
  1042.           (save-excursion
  1043.          (cond
  1044.           ((not ispell-message-text-end) (point-max))
  1045.           ((char-or-string-p ispell-message-text-end)
  1046.            (if (re-search-forward ispell-message-text-end nil 'end)
  1047.                (match-beginning 0)
  1048.              (point-max)))
  1049.           (t (funcall ispell-message-text-end))))))
  1050.         (search-limit ; Search limit which won't stop in middle of citation
  1051.          (+ limit (length cite-regexp)))
  1052.         )
  1053.      ;; Check the subject
  1054.      (save-excursion
  1055.        (let ((case-fold-search t)
  1056.          (message-begin (point)))
  1057.          (goto-char (point-min))
  1058.          ;; "\\s *" matches newline if subject is empty
  1059.          (if (and (re-search-forward "^Subject:[\t ]*" message-begin t)
  1060.               (not (looking-at "re\\>")))
  1061.          (setq continue
  1062.                (ispell-region (- (point) 1)
  1063.                       (progn
  1064.                        (end-of-line)
  1065.                        (while (looking-at "\n[ \t]")
  1066.                      (end-of-line 2))
  1067.                        (point))))
  1068.            )))
  1069.  
  1070.     ;; Check the body.
  1071.     (while (and (< (point) limit) continue)
  1072.       ;; Skip across text cited from other messages.
  1073.       (while (and (looking-at (concat "^[ \t]*$\\|" cite-regexp))
  1074.               (< (point) limit))
  1075.         (forward-line 1))
  1076.       (if (< (point) limit)
  1077.           ;; Check the next batch of lines that *aren't* cited.
  1078.           (let ((start (point)))
  1079.         (if (re-search-forward
  1080.              (concat "^\\(" cite-regexp "\\)") search-limit 'end)
  1081.             (beginning-of-line))
  1082.         (if (> (point) limit) (goto-char limit))
  1083.         (let ((case-fold-search old-case-fold-search))
  1084.           (save-excursion
  1085.             (setq continue (ispell-region (- start 1) (point))))))))))))
  1086.  
  1087. (provide 'ispell)
  1088.  
  1089. ;;; ispell.el ends here
  1090.